perm filename TRICKS.MF[MF,DEK] blob sn#776975 filedate 1984-11-19 generic text, type T, neo UTF8
% try to make a |x| macro (assuming that there's no nesting),
% with delimiters leftabs rightabs and | switching back-and-forth?

% this implements the former "readtokens s":
def stringmac(expr s)(suffix $) =
expandafter aux expandafter ( scantokens s)($) enddef;
def aux(text s,$) = def $=s enddef enddef;
stringmac("hello there",a); showtoken a;
stringmac(readstring,b); showtoken b;

% These things by JDH might give material for Appendix D, or exercises

% Here's a beaut: one can call inorder(a,b,c), for example!
% See if a list of at least two numerics, strings, or pairs is in order.

def inorder(expr s)(text t) =
    ((s  for i:=t: <=i) and (i endfor  >= s))
enddef;
% comment by DEK: the last >=s could be replaced by "tautology 0"
% where x tautology y = true

% Argument is a list of at least two numeric or pair expressions.

def equally_spaced(expr s)(text t) =
    begingroup save dD;
    if pair s: pair dD; fi
    s  for i:=t: -i=i endfor -dD;
    endgroup
enddef;


% Draw a grid with vertical lines at all the positions in xlist and horizontal
% lines at all the positions in ylist.  Both xlist and ylist can either be
% explicit lists of point numbers or `thru' constructs.
%    The calling syntax should be `grid(...)(...)' exactly as if there were two
% text parameters.  The tricky `fingrid' routine parses the other parameter
% and uses the x-list stored in the `xxl' array.  This allows expr parameters
% of other macros to appear in both lists.
% (The strange capitalizations reduce the chance of name conflict.)

def grid(text xlist) =
    begingroup save xmiN, xmaX, ymiN, ymaX, xXl, xXllng;
    xXllng:=0;
    for i:=xlist: xXl[incr(xXllng)] = i; endfor
    xmiN = min(xlist);
    xmaX = max(xlist);
    fingrid
enddef;

def fingrid(text ylist) =
    ymiN = min(ylist);
    ymaX = max(ylist);
    for i:=1 thru xXllng: proofrule((xXl[i],ymiN), (xXl[i],ymaX)); endfor
    for i:=ylist: proofrule((xmiN,i), (xmaX,i)); endfor
    endgroup
enddef;

% Window allocation

% We allocate windows 1, 2, ..., 15.  Window 0 is perminantly reserved for
% other uses.
% The window allocation routines treat the screen as an infinite rectangle
% screencols pixels wide.  Windows are sequentially allocated in rows.  All
% windows in a row are lined up at their top edge at the height of the lowest
% bottom edge in the previous row.
% To do this allocation we maintain the screen coordinate pair screencorner, where
% the upper-left corner of the next window will be if it fits on the row.  We
% also maintain screenbot, the height of the next row if any.

nwindows = 0;
pair screen_corner; screen_corner=(0,0);% upper left corner of space for next window
screen_bot = 0;				% vertical pos for next row of windows

def wipescreen =
    for i:=1 thru nwindows: display blankpicture on i; endfor
    nwindows := 0;
    screen_corner := origin
enddef;


% Given the MF coordinates of any two opposite corners of a rectangle, map that
% rectangle to the next available screen rectangle and open it as window number
% window@#.

vardef newwindow@#(expr a, b) =
    %begingroup
    save uplft, lowrt;
    pair uplft, lowrt;
    if showing:
      if nwindows=15: errmessage "No more windows left!";
      else: window@#:=incr(nwindows);
	    uplft = (min(xpart a,xpart b), max(ypart a,ypart b));
	    lowrt = (a + b - 2uplft) rotated 90;                  % screen coordinates
	    if ypart(screen_corner+lowrt) > screencols:
		screen_corner := (screenbot,0);
	    fi;
	    openwindow window@# from screen_corner to screen_corner+lowrt
		       at uplft;
	    screen_bot := max(screen_bot, xpart(screen_corner+lowrt));
	    screen_corner := screen_corner + (0, ypart lowrt);
      fi
    fi
    %endgroup
enddef;

% Here is a routine very much like draw except that the stuff after it has to
% be enclosed in parentheses and it shouldn't execute any other drawing
% commands.  (That is rather unlikely anyway.)
% The difference is that the ends are cut off flush.  The cut line is defined
% by the points where the pen is tangent to the envelope at either end of the
% path, where tangents to the envelope are given by the version of penoffset
% defined above.  The angle of the cut should be exactly right for circular
% pens when the terminal directions are multiples of 45 degrees (if DEK would
% change make_pen to guarantee the symmetry of circular pens), but may
% sometimes stray significantly from the ideal value.  Anything much fancier
% would require extrapolating the path and would be of questionable value for
% non-circular pens.

let normalwithpen = withpen;
let normalwithweight = withweight;

def drawflush(text spec) =
    begingroup save ptH, peN, wT, lL;	% strange names to avoid conflict
    pen peN; path ptH;
    peN = defaultpen;
    wT = 1;
	def withpen = ;peN := enddef;
	def withweight = ;wT := enddef;
	ptH = spec;
	let withpen = normalwithpen;
	let withweight = normalwithweight;
    lL = length ptH;
    draw ptH withpen peN;
    erase_end(point lL of ptH, -direction lL of ptH, peN, wT);
    erase_end(point 0 of ptH, direction 0 of ptH, peN, wT);
    endgroup
enddef;

% This is a little trickey but it just sets the default weight to -1.

def undrawflush(text spec) =
    drawflush(origin withweight -1; ptH:= spec)
enddef;


% Erase the end of pen pn assuming that it starts at point p in direction d
% and was drawn with weight wt.

def erase_end(expr p, d, pn, wt) =
    begingroup
    save e, x, y, u;
    path u;			% path enclosing everything done to e
    edges e;			% edges to be subtracted from the current picture
    interim smoothing:=0;
    interim autorounding:=0;
    e := nulledges;
    addto e doublepath p withpen pn;		% now e contains a pen image
    z0 = p + Penoffset d of pn;
    z2 = p + penoffset d rotated 90 of pn;
    z4 = p + Penoffset -d of pn;
    z6 = p + penoffset d rotated -90 of pn;
    parallelto(d, 0,1,7);
    parallelto(d, 3,4,5);
    perpto(d, 1,2,3);
    perpto(d, 5,6,7);
    addto e contour z0~..z1~..z3~..z4~..cycle withweight -1;
    cull e by (-4095,1);			% now e is part of pen to be removed
    if wt>0:
	u = z1~..z3~..z5~..z7~..cycle;
	addto e contour u withweight -1;	% wts: bad pix 0, others in u -1
	cull e by (-1,4095);			% wts: bad pix 0, others in u 1
	addto e contour u withweight -1;	% wts: bad pix -1, all others 0
    fi
    for i:=1 thru abs round wt: addto currentpicture also e; endfor
    endgroup
enddef;

def =: = getexp 0; getsuf enddef;
tertiarydef e getexp garbage =
    begingroup save savE; pair savE; savE=e
enddef;
vardef getsuf.z@# =
    x@#:=xpart savE; y@#:=ypart savE; endgroup
enddef;

% A boolean test of the form `if p or q: P', where q cannot be safely
% evaluated when p is true cannot be easily be performed with if...else
% without repeating P.  The following macros get around this at the cost
% of having to put extra parentheses around the second argument.
% e.g. if p cor(q): P ...; similarly, if p cand(q): ...

primarydef p startif garbage = if p: enddef;

def cand(text q) = startif true q else: false fi enddef;

def cor(text q) = startif true true else: q fi enddef;

% Generalized equality test
tertiarydef a == b =
if numeric a or string a or pair a: (a=b)
elseif boolean a: (a and b or not (a or b))
elseif transform a:
    (true
     for i:=N,E,origin: and (i transformed a = i transformed b) endfor)
elseif path a:
    if (cycle a == cycle b) and (length a = length b)
       and (point 0 of a = point 0 of b):
	    begingroup save t; boolean t;
	    t=true;
	    for i:=1 thru length a:
		t:=(point i of a = point i of b) and
		   (precontrol i of a = precontrol i of b) and
		   (postcontrol i-1 of a = postcontrol i-1 of b);
		exitunless t;
	    endfor
	    t endgroup
     else: false
    fi
elseif pen a: (makepath a == makepath b)
else: errmessage "I can't compare edges.  Ask DEK"
fi
enddef;